package com.pet.item.utils;

import cn.hutool.json.JSONUtil;
import com.pet.item.domain.po.Item;
import com.pet.item.mapper.BrowseHistoryMapper;
import com.pet.item.service.IItemService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.stream.Collectors;

import static com.pet.common.utils.RedisConstants.RECOMMEND_USER_KEY;

@Component
@RequiredArgsConstructor
public class ItemCFUtil {
    private final IItemService itemService;
    private final BrowseHistoryMapper browseHistoryMapper;
    private final StringRedisTemplate stringRedisTemplate;
    private final int maxRecommendNum = 20;

    // 通过计算余弦相似度并取TopN, 保存为uid的用户生成的N个推荐商品到Redis
    public List<Integer> recommend(int uid) {
        // 其他用户浏览的商品列表
        List<Integer> itemIds;
        // 所有商品列表
        Map<Integer, Item> idItemMap = itemService.list().stream()
                .collect(Collectors.toMap(Item::getId, item -> item));
        List<Integer> items = new ArrayList<>(idItemMap.keySet());
        // List<Integer> items = itemService.list().stream()
        //         .map(Item::getId)
        //         .collect(Collectors.toList());
        // 当前矩阵
        int[][] curMatrix = new int[items.size() + 5][items.size() + 5];
        // 共现矩阵
        int[][] comMatrix = new int[items.size() + 5][items.size() + 5];
        // 浏览过每个物品的人数
        int[] N = new int[items.size() + 5];
        // 所有用户列表
        List<Integer> userIds = browseHistoryMapper.getAllUser();

        for (Integer userId : userIds) {
            // 当前用户则跳过
            if (userId == uid)
                continue;
            // 其他用户浏览的商品列表
            itemIds = browseHistoryMapper.getItemByUser(userId);

            for (int i = 0; i < items.size(); i++)
                for (int j = 0; j < items.size(); j++)
                    // 清空矩阵
                    curMatrix[i][j] = 0;
            for (int i = 0; i < itemIds.size(); i++) {
                int pid1 = itemIds.get(i);
                ++N[pid1];
                for (int j = i + 1; j < itemIds.size(); j++) {
                    int pid2 = itemIds.get(j);
                    ++curMatrix[pid1][pid2];
                    // 两两加一
                    ++curMatrix[pid2][pid1];
                }
            }
            // 累加所有矩阵, 得到共现矩阵
            for (int i = 0; i < items.size(); i++) {
                for (int j = 0; j < items.size(); j++) {
                    int pid1 = items.get(i), pid2 = items.get(j);
                    comMatrix[pid1][pid2] += curMatrix[pid1][pid2];
                    comMatrix[pid1][pid2] += curMatrix[pid1][pid2];
                }
            }
        }
        // 预处理的列表
        TreeSet<Item> preList = new TreeSet<Item>(new Comparator<Item>() {
            @Override
            public int compare(Item i1, Item i2) {
                if (i1.getSimilarity() != i2.getSimilarity())
                    return (int) (i1.getSimilarity() - i2.getSimilarity()) * 100;
                else
                    return i1.getViews() - i2.getViews();
            }
        });
        // 当前用户浏览的商品列表
        itemIds = browseHistoryMapper.getItemByUser(uid);
        // 判重数组
        boolean[] used = new boolean[items.size() + 5];
        for (Integer itemId : itemIds) {
            // 既浏览过i又浏览过j的人数
            int Nij = 0;
            // 相似度
            double Wij;
            // 当前的商品
            Item tmp;

            int i = itemId;
            for (Integer item : items) {
                if (itemId == item)
                    continue;
                int j = item;

                Nij = comMatrix[i][j];
                // 计算余弦相似度
                Wij = (double) Nij / Math.sqrt(N[i] * N[j]);

                tmp = idItemMap.get(item);
                // tmp = itemService.getById(item);
                tmp.setSimilarity(Wij);

                if (used[tmp.getId()])
                    continue;
                preList.add(tmp);
                used[tmp.getId()] = true;
            }
        }
        // 生成的推荐结果
        List<Integer> recommendItemIds = new ArrayList<>();
        for (int i = 0; preList.size() > 0 && i < maxRecommendNum; i++) {
            recommendItemIds.add(preList.pollLast().getId());
            preList.pollLast();
        }
        if (recommendItemIds.size() < maxRecommendNum) {
            // 推荐数量不满N个, 补足浏览数最高的商品
            supplyTopNItem2RecommendList(recommendItemIds);
        }
        System.out.println("已生成针对用户id为" + uid + "的推荐: " + recommendItemIds);
        String key = RECOMMEND_USER_KEY + uid;
        String idsStr = JSONUtil.toJsonStr(recommendItemIds);
        stringRedisTemplate.opsForValue().set(key, idsStr);
        return recommendItemIds;
    }

    private void supplyTopNItem2RecommendList(List<Integer> recommendItemIds) {
        // 将浏览数最高的商品补入推荐列表, 直至满N个(考虑重复)
        List<Integer> itemIds = itemService.lambdaQuery()
                .select(Item::getId)
                .orderByDesc(Item::getViews)
                .list().stream()
                .map(Item::getId)
                .collect(Collectors.toList());

        // 将推荐列表转换为Set以提高查找效率
        Set<Integer> recommendItemIdSet = new HashSet<>(recommendItemIds);

        // 补充商品到推荐列表
        for (Integer itemId : itemIds) {
            if (!recommendItemIdSet.contains(itemId)) {
                recommendItemIds.add(itemId);
                recommendItemIdSet.add(itemId);
                // 如果达到最大推荐数, 则停止添加
                if (recommendItemIds.size() >= maxRecommendNum) {
                    break;
                }
            }
        }
    }
}
